package com.ruoyi.web.controller.tool;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONException;
import com.alibaba.fastjson.JSONObject;
import com.fazecast.jSerialComm.SerialPort;
import com.fazecast.jSerialComm.SerialPortDataListener;
import com.fazecast.jSerialComm.SerialPortEvent;
import com.fazecast.jSerialComm.SerialPortInvalidPortException;
import com.ruoyi.system.websocket.WebSocketServer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.websocket.EncodeException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.*;

import static reactor.core.publisher.Operators.validate;

public class SerialPortUtil {

    private static final Logger logger = LoggerFactory.getLogger(SerialPortUtil.class);

    private static SerialPortUtil serialPortUtil = null;
    private static final char[] DIGITS_LOWER = new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
    private static final char[] DIGITS_UPPER = new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};

    static {
        //在该类被ClassLoader加载时就初始化一个SerialTool对象
        if (serialPortUtil == null) {
            serialPortUtil = new SerialPortUtil();
        }
    }

    //私有化SerialTool类的构造方法，不允许其他类生成SerialTool对象
    private SerialPortUtil() {
    }

    /**
     * 查询可用端口
     *
     * @return 串口名List
     */
    public static List<String> listPortName() {
        List<String> result = new ArrayList<>();
        SerialPort[] serialPorts = SerialPort.getCommPorts();//查找所有串口

        // 获得当前所有可用端口
        for (SerialPort port : serialPorts) {
            logger.info("Port:" + port.getSystemPortName());//打印串口名称，如COM4
            logger.info("PortDesc:" + port.getPortDescription());//打印串口类型，如USB Serial
            logger.info("PortDesc:" + port.getDescriptivePortName());//打印串口的完整类型，如USB-SERIAL CH340(COM4)
            // 将可用端口名添加到List并返回该List
            result.add(port.getSystemPortName());
        }

        return result;
    }

    private static void processData(String raw) {
        // 解析JSON数据
        try {
            JSONObject data = JSON.parseObject(raw);

            // 数据校验
//            if (!validate(data)) {
//                throw new IllegalArgumentException("Invalid data format");
//            }

            // 转换为业务对象
            String strArr = data.getString("value");
            String[] arr = strArr.split(",");
            List<Integer> intList = new ArrayList<>();
            for (String arrStr : arr) {
                intList.add(Integer.valueOf(arrStr.trim()));
            }
            logger.info("接收串口消息成功:" + intList);
            // 发送到WebSocket
            WebSocketServer.sendMessageToUser("1",JSON.toJSONString(intList));

        } catch (JSONException e) {
            System.err.println("数据解析失败: " + e.getMessage());
        } catch (IOException e) {
            throw new RuntimeException(e);
        } catch (EncodeException e) {
            throw new RuntimeException(e);
        }
    }

    public static void doSerialPortBusiness() {
        SerialPort comPort = SerialPort.getCommPort("COM1");
        comPort.setBaudRate(9600);
        comPort.setNumDataBits(8);
        comPort.setNumStopBits(1);
        comPort.setParity(SerialPort.NO_PARITY);

        if (comPort.openPort()) {
            comPort.addDataListener(new SerialPortDataListener() {
                @Override
                public int getListeningEvents() {
                    return SerialPort.LISTENING_EVENT_DATA_AVAILABLE;
                }

                @Override
                public void serialEvent(SerialPortEvent event) {
                    if (event.getEventType() != SerialPort.LISTENING_EVENT_DATA_AVAILABLE) return;

                    byte[] buffer = new byte[comPort.bytesAvailable()];
                    comPort.readBytes(buffer, buffer.length);

                    String rawData = new String(buffer);
                    processData(rawData);
                }
            });
        }
        // 获取所有可用的端口
//        List<String> listPorts = listPortName();
//        if (null != listPorts && listPorts.size() == 0) {
//            logger.warn("无可用端口");
//            return;
//        }
//        for (final String portName : listPorts) {
//            logger.info("可用端口：{}", portName);
//            //使用匿名内部类来创建线程实例
//            Thread tBusiness = new Thread() {
//                @Override
//                public void run() {
//                    // 串口通讯核心业务处理
//                    try {
//                        doSerialPortBusiness(portName);
//                    } catch (Exception e) {
//                        logger.error("串口通讯核心业务处理异常：", e);
//                    }
//                }
//            };
//            tBusiness.start();
//        }
    }

    /**
     * 打开电脑上指定的串口
     *
     * @param portName 串口名
     * @param baudRate 波特率
     * @param dataBits 数据位
     * @param stopBits 停止位
     * @param parity   校验位
     * @return 串口对象
     */
    public static SerialPort open(String portName, Integer baudRate, Integer dataBits,
                                  Integer stopBits, Integer parity) {

        SerialPort serialPort = null;
        try {
            logger.info(String.format("开始打开串口：portName=%s,baudrate=%s,datebits=%s,stopbits=%s,parity=%s",
                    portName, baudRate, dataBits, stopBits, parity));
            // 通过端口名识别端口
            serialPort = SerialPort.getCommPort(portName);
            // 打开端口，并给端口名字和一个timeout（打开操作的超时时间）
            /**
             * open(String TheOwner, int i)：打开端口
             * TheOwner 自定义一个端口名称，随便自定义即可
             * i：打开的端口的超时时间，单位毫秒，超时则抛出异常：PortInUseException if in use.
             * 如果此时串口已经被占用，则抛出异常：gnu.io.PortInUseException: Unknown Application
             */
            serialPort.openPort(2000);
            // 判断端口是不是串口
            if (serialPort.isOpen()) {
                // 设置一下串口的波特率等参数
                /**
                 * 设置串口参数：setSerialPortParams( int b, int d, int s, int p )
                 * b：波特率（baudrate）
                 * d：数据位（datebits），SerialPort 支持 5,6,7,8
                 * s：停止位（stopbits），SerialPort 支持 1,2,3
                 * p：校验位 (parity)，SerialPort 支持 0,1,2,3,4
                 * 如果参数设置错误，则抛出异常：gnu.io.UnsupportedCommOperationException: Invalid Parameter
                 * 此时必须关闭串口，否则下次 portIdentifier.open 时会打不开串口，因为已经被占用
                 */
                serialPort.setComPortTimeouts(SerialPort.TIMEOUT_READ_BLOCKING | SerialPort.TIMEOUT_WRITE_BLOCKING, 1000, 1000);//设置超时
//                serialPort.serRTS();//设置RTS。也可以设置DTR
                //设置串口的控制流，可以设置为disabled，或者CTS, RTS/CTS, DSR, DTR/DSR, Xon, Xoff, Xon/Xoff等
                serialPort.setFlowControl(SerialPort.FLOW_CONTROL_DISABLED);
                /**一次性设置所有的串口参数，第一个参数为波特率，默认9600；第二个参数为每一位的大小，
                 * 默认8，可以输入5到8之间的值；第三个参数为停止位大小，只接受内置常量，
                 * 可以选择(ONE_STOP_BIT, ONE_POINT_FIVE_STOP_BITS, TWO_STOP_BITS)；
                 * 第四位为校验位，同样只接受内置常量，可以选择 NO_PARITY, EVEN_PARITY, ODD_PARITY, MARK_PARITY,SPACE_PARITY。
                 */
                serialPort.setComPortParameters(baudRate, dataBits, SerialPort.ONE_STOP_BIT, SerialPort.NO_PARITY);
                logger.info("打开串口{}成功", portName);
                return serialPort;
            } else {
                logger.error("当前端口 " + portName + " 不是串口...");
            }
        } catch (SerialPortInvalidPortException e) {
            logger.warn("串口 " + portName + " 无效...");
        } catch (Exception e) {
            logger.warn("串口参数设置错误，关闭串口，数据位[5-8]、停止位[1-3]、验证位[0-4]...");
            //此时必须关闭串口，否则下次 open 时会打不开串口，因为已经被占用
            if (serialPort != null) {
                serialPort.closePort();
            }
        }
        logger.error("打开串口 " + portName + " 失败...");
        return serialPort;
    }

    /**
     * 串口通讯核心业务处理
     *
     * @param portName 端口名
     */
    public static void doSerialPortBusiness(String portName) throws Exception {
        // 打开串口com1
        final SerialPort serialPort = open(portName, 115200, 8, 1, 0);

        int count = 0;
        // 循环获取主机相关信息---时间间隔：2s
        while (count < 20) {
//            read(serialPort);
            count++;
        }
    }

    /**
     * 往串口发送数据
     *
     * @param serialPort 串口对象
     */
    public static void sendDataToComPort(SerialPort serialPort, byte[] orders) {
        try {
            if (serialPort == null || !serialPort.isOpen()) {
                logger.error("serialPort 为null，取消数据发送...");
                return;
            }
            logger.info("往串口 " + serialPort.getSystemPortName() + " 发送数据开始...");
            serialPort.writeBytes(orders, orders.length);
            logger.info("往串口 " + serialPort.getSystemPortName() + " 发送数据：" + Arrays.toString(orders) + " 完成...");
        } catch (Exception e) {
            logger.error("往串口发送数据失败：", e);
        }
    }

    /**
     * 从串口读取数据
     *
     * @param serialPort
     * @return
     */
    public static byte[] read(SerialPort serialPort) throws IOException {
        byte[] result = {};
        if (serialPort == null) {
            return result;
        }
        InputStream inputStream = null;
        try {
            inputStream = serialPort.getInputStream();

            // 缓冲区大小为1个字节，可根据实际需求修改
            byte[] readBuffer = new byte[inputStream.available()];

            while (inputStream.available() > 0) {
                inputStream.read(readBuffer);
                result = addAll(result, readBuffer);
                logger.info(String.format("串口%s读取数据：" + encodeHexStr(result), serialPort.getSystemPortName()));
            }
        } catch (IOException e) {
            logger.error("串口{}读取数据错误", serialPort.getSystemPortName(), e);
        } finally {

            close(serialPort);
        }
        return result;
    }

    public static String encodeHexStr(byte[] data) {
        return encodeHexStr(data, true);
    }

    public static String encodeHexStr(byte[] data, boolean toLowerCase) {
        return encodeHexStr(data, toLowerCase ? DIGITS_LOWER : DIGITS_UPPER);
    }

    private static String encodeHexStr(byte[] data, char[] toDigits) {
        return new String(encodeHex(data, toDigits));
    }

    private static char[] encodeHex(byte[] data, char[] toDigits) {
        int len = data.length;
        char[] out = new char[len << 1];
        int i = 0;

        for(int j = 0; i < len; ++i) {
            out[j++] = toDigits[(240 & data[i]) >>> 4];
            out[j++] = toDigits[15 & data[i]];
        }

        return out;
    }

    public static byte[] addAll(byte[]... arrays) {
        if (arrays.length == 1) {
            return arrays[0];
        } else {
            int length = 0;
            byte[][] var2 = arrays;
            int var3 = arrays.length;

            int var4;
            for (var4 = 0; var4 < var3; ++var4) {
                byte[] array = var2[var4];
                if (null != array) {
                    length += array.length;
                }
            }

            byte[] result = new byte[length];
            length = 0;
            byte[][] var8 = arrays;
            var4 = arrays.length;

            for (int var9 = 0; var9 < var4; ++var9) {
                byte[] array = var8[var9];
                if (null != array) {
                    System.arraycopy(array, 0, result, length, array.length);
                    length += array.length;
                }
            }

            return result;
        }
    }

    /**
     * 关闭inputstream
     *
     * @param serialPort
     */
    public static void close(SerialPort serialPort) {
        if (serialPort != null) {
            serialPort.closePort();
        }
    }

    /**
     * 获取提供服务的SerialTool对象
     *
     * @return serialPortUtil
     */
    public static SerialPortUtil getSerialPortUtil() {
        if (serialPortUtil == null) {
            serialPortUtil = new SerialPortUtil();
        }
        return serialPortUtil;
    }




    /**
     * 往串口发送数据
     *
     * @param serialPort 串口对象
     * @param order      待发送数据
     *                   //     * @throws SendDataToSerialPortFailure        向串口发送数据失败
     *                   //     * @throws SerialPortOutputStreamCloseFailure 关闭串口对象的输出流出错
     */
    public void sendToPort(SerialPort serialPort, byte[] order) {
        OutputStream out = null;
        try {
            out = serialPort.getOutputStream();
            out.write(order);
            out.flush();
        } catch (IOException e) {
//            throw new SendDataToSerialPortFailure();
            e.printStackTrace();
        } finally {
            try {
                if (out != null) {
                    out.close();
                    out = null;
                }
            } catch (IOException e) {
//                throw new SerialPortOutputStreamCloseFailure();
                e.printStackTrace();
            }
        }
    }

    /**
     * 从串口读取数据
     *
     * @param serialPort 当前已建立连接的SerialPort对象
     * @return 读取到的数据
     * //     * @throws ReadDataFromSerialPortFailure     从串口读取数据时出错
     * //     * @throws SerialPortInputStreamCloseFailure 关闭串口对象输入流出错
     */
    public byte[] readFromPort(SerialPort serialPort) {

        InputStream in = null;
        byte[] bytes = null;

        try {
            in = serialPort.getInputStream();
            int bufflenth = in.available();        //获取buffer里的数据长度

            while (bufflenth != 0) {
                bytes = new byte[bufflenth];    //初始化byte数组为buffer中数据的长度
                in.read(bytes);
                bufflenth = in.available();
            }
        } catch (IOException e) {
            e.printStackTrace();
//            throw new ReadDataFromSerialPortFailure();
        } finally {
            try {
                if (in != null) {
                    in.close();
                    in = null;
                }
            } catch (IOException e) {
                e.printStackTrace();
//                throw new SerialPortInputStreamCloseFailure();
            }
        }

        return bytes;

    }

    public static void initWebSocket() throws IOException, EncodeException {
        String[] arr = new String[]{"9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31", "32", "33", "34", "35"};
        List<Integer> intList = new ArrayList<>();
        for (String arrStr : arr) {
            intList.add(Integer.valueOf(arrStr.trim()));
        }
        logger.info("接收串口消息成功:" + intList);
        // 发送到WebSocket
        WebSocketServer.sendMessageToUser("1",JSON.toJSONString(intList));
    }
}
